package wangjg.commons.bean;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * 
 * @author wangjg
 *
 */
public class BeanUtil {
	
	public static void removeItem(List list, Object eqVal){
		Iterator it = list.iterator();
		while(it.hasNext()){
			Object val = it.next();
			if(val==null && eqVal==null){
				it.remove();
			}else if(val!=null && (val==eqVal || val.equals(eqVal))){
				it.remove();
			}
		}
	}
	
	public static void removeByProperty(List list, String property, Object value){
		Iterator it = list.iterator();
		while(it.hasNext()){
			Object bean = it.next();
			Object pv = getProperty(bean, property);
			if(pv==null && value==null){
				it.remove();
			}else if(pv!=null && (pv==value || pv.equals(value))){
				it.remove();
			}
		}
	}
	
	/**
	 * 合并部分相同属性的值,相当于select c2,c3,sum(c1) from t group by c2,c3
	 * @param list
	 * @param groupPps 分组属性
	 * @param sumPps 计算的属性
	 * @return
	 * @throws Exception
	 */
	public static List merge(List list, String[] groupPps, String[] sumPps) throws Exception{
		List result = new ArrayList();
		Map<String,List> map = group(list, groupPps);
		for(Entry<String, List> entry : map.entrySet()){
			List groupList = entry.getValue();
			Object newItem = clone(groupList.get(0));
			result.add(newItem);
			for(String sumPp:sumPps){
				Double groupSum = sum(groupList, sumPp);
				setNumberProperty(newItem, sumPp, groupSum);
			}
		}
		return result;
	}
	
	private static void setNumberProperty(final Object bean, final String property, final Double value) throws Exception{
		Class calzz = getPropertyType(bean, property);
		if(calzz.equals(Double.class)||calzz.equals(double.class)){
			setProperty(bean, property, value);
		}else if(calzz.equals(Float.class)||calzz.equals(float.class)){
			setProperty(bean, property, value.floatValue());
		}else if(calzz.equals(Long.class)||calzz.equals(long.class)){
			setProperty(bean, property, value.longValue());
		}else if(calzz.equals(Integer.class)||calzz.equals(int.class)){
			setProperty(bean, property, value.intValue());
		}else if(calzz.equals(Short.class)||calzz.equals(short.class)){
			setProperty(bean, property, value.shortValue());
		}else if(calzz.equals(Byte.class)||calzz.equals(byte.class)){
			setProperty(bean, property, value.shortValue());
		}
		
	}
	
	private static Class getPropertyType(Object bean, String property) {
		try {
			PropertyDescriptor pd = new PropertyDescriptor(property, bean.getClass());
			return pd.getPropertyType();
		} catch (IntrospectionException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private static Double sum(List list, String sumProperty){
		Double result = 0.0;
		for(Object bean : list){
			try {
				Object value = getProperty(bean, sumProperty);
				result += Double.valueOf(""+value);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
			
		}
		return result;
	}
	
	public static <T> T clone(Object bean){
		Object newBean;
		try {
			newBean = bean.getClass().newInstance();
			copyProperties(bean, newBean, null);
			return (T) newBean;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public static Map<String,List> group(List list, String...pps){
		Map<String,List> map = new LinkedHashMap<String,List>();
		for(Object bean : list){
			
			StringBuilder keySb = new StringBuilder();
			for(int i=0;i<pps.length;i++){
				String property = pps[i];
				try {
					if(i!=0){
						keySb.append(',');
					}
					Object pv = getProperty(bean, property);
					keySb.append(pv);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			String key = keySb.toString();
			
			List valueList = map.get(key);
			if(valueList==null){
				valueList = new ArrayList();
				map.put(key, valueList);
			}
			valueList.add(bean);
		}
		return map;
	}
	
	public static Map count(List list, String groupby){
		Map map = new HashMap();
		for(Object bean : list){
			try {
				Object pv = getProperty(bean, groupby);
				if(!map.containsKey(pv)){
					map.put(pv, 1);
				}else{
					Integer n = (Integer) map.get(pv);
					n += 1;
					map.put(pv, n);
				}
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
			
		}
		return map;
	}
	
	public static void bind(Object bean ,Map<?,?> map){
		bind(bean, map , null, null);
	}
	
	public static void bind(Object bean ,Map<?,?> map, String[] allowedFields){
		bind(bean, map, allowedFields, null);
	}
	
	public static void bind(Object bean ,Map map, String[] allowedFields ,String[] disallowedFields){
		HashMap _map = new HashMap();
		if(allowedFields!=null && allowedFields.length>0){
			for(int i=0;i<allowedFields.length;i++){
				_map.put(allowedFields[i], map.get(allowedFields[i]));
			}
		}else{
			_map.putAll(map);
		}
		if(disallowedFields!=null && disallowedFields.length>0){
			for(int i=0;i<disallowedFields.length;i++){
				_map.remove(disallowedFields[i]);
			}
		}
		Set<Entry> entrySet = _map.entrySet();
		for(Entry entry:entrySet){
			String property = (String) entry.getKey();
			if(property==null||property.isEmpty()){
				continue;
			}
			Object value = entry.getValue();
			setProperty(bean, property, value);
		}
	}
	
	public static Map<String,String> getStringMap(Object bean, boolean excludeNull, String dateFormat){
		SimpleDateFormat df = new SimpleDateFormat(dateFormat);
		Map<String,Object> map = getObjectMap(bean, excludeNull);
		HashMap<String,String> smap = new HashMap<String,String>();
		Set<Entry<String, Object>> entrySet = map.entrySet();
		for(Entry<String, Object> entry:entrySet){
			String value=null;
			if(entry.getValue() instanceof Date){
				value = df.format((Date)entry.getValue());
			}else{
				value = ""+entry.getValue();
			}
			smap.put(entry.getKey(), value);
		}
		return smap;
	}
	
	public static Map<String,Object> getObjectMap(final Object source, final boolean excludeNull){
		final HashMap<String,Object> map = new HashMap<String,Object>();
		
		Handle handle=new Handle(){
			public void handle(Class c, Method writeMethod, Method readMethod, String property, Object pv) {
				if(pv!=null || !excludeNull){
					map.put(property, pv);
				}
			}
		};
		try {
			traverse(source.getClass(), source, true, handle);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return map;
	}

	public static <T> List<T> cloneList(List<T> list, String[] ignoreFields){
		if(list==null){
			return null;
		}
		List<T> newList = new ArrayList<T>();
		for(T bean:list){
			if(bean==null){
				newList.add(null);
				continue;
			}
			try {
				T newBean = (T) bean.getClass().newInstance();
				copyProperties(bean, newBean, ignoreFields);
				newList.add(newBean);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return newList;
	}
	
	public static void copyProperties(final Object source, final Object target, final String[] ignoreFields) throws Exception{
		Handle handle=new Handle(){
			public void handle(Class c, Method writeMethod, Method readMethod, String property, Object pv) {
				if(ignoreFields!=null){
					for(String s:ignoreFields){
						if(property.equals(s)){
							return;
						}
					}	
				}
				if(writeMethod!=null && readMethod!=null){
					setProperty(target, property, pv);
				}
			}
		};
		traverse(source.getClass(), source, true ,handle);
	}
	
	/**
	 * @param list 背分组的对象集合
	 * @param property 分组属性
	 * @return Map<Bean.property, List<Bean>>
	 */
	public static Map groupByProperty(List list, String property){
		HashMap map = new HashMap();
		for(Object bean:list){
			try {
				Object pv = getProperty(bean, property);
				List pList = (List) map.get(pv);
				if(pList==null){
					pList = new ArrayList();
					map.put(pv, pList);
				}
				pList.add(bean);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return map;
	}
	
	public static Map groupByProperty(List list, String property, boolean mix){
		HashMap map = new HashMap();
		for(Object bean:list){
			try {
				Object pv = getProperty(bean, property);
				Object pObject = map.get(pv);
				if(pObject==null){
					map.put(pv, bean);
				}else{
					if(pObject instanceof List){
						List pList = (List) pObject;
						pList.add(bean);
					}else{
						List pList = new ArrayList();
						pList.add(pObject);
						pList.add(bean);
						map.put(pv, pList);
					}
				}
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return map;
	}
	
	/**
	 * 根据唯一属性分组对象集合,一个key对应一个对象
	 * @param list
	 * @param property
	 * @return Map<Bean.property, Bean>
	 */
	public static Map groupByUniqueProperty(List list, String property){
		HashMap map = new HashMap();
		for(Object bean:list){
			try {
				Object pv = getProperty(bean, property);
				if(!map.containsKey(pv)){
					map.put(pv, bean);
				}else{
					throw new RuntimeException("duplicate property : "+property+"=" + pv);
				}
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return map;
	}
	
	public static Object[] getPropertyArray(List list, String property){
		Object[] array = new Object[list.size()];
		for(int i=0;i<list.size();i++){
			Object bean = list.get(i);
			try {
				Object pv = getProperty(bean, property);
				array[i] = pv;
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return array;
	}
	
	public static <T> List<T> getPropertyList(List list, String property){
		List<T> plist = new ArrayList<T>();
		for(int i=0;i<list.size();i++){
			Object bean = list.get(i);
			try {
				T pv = (T) getProperty(bean, property);
				plist.add(pv);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return plist;
	}
	
	public static List getDistinctPropertyList(List list, String property){
		ArrayList plist = new ArrayList();
		for(int i=0;i<list.size();i++){
			Object bean = list.get(i);
			try {
				Object pv = getProperty(bean, property);
				if(!plist.contains(pv)){
					plist.add(pv);
				}
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return plist;
	}
	
	static Object getProperty(Object bean, String property){
		Object pv = bean;
		if(Map.class.isAssignableFrom(pv.getClass())){
			pv = ((Map)pv).get(property);
		}else if(List.class.isAssignableFrom(pv.getClass())){
			pv = ((List)pv).get(Integer.parseInt(property));
		}else if(pv.getClass().isArray()){
			pv = Array.get(pv, Integer.parseInt(property));
		}else{
			pv = getSimpleProperty(pv, property);	
		}
		return pv;
	}

	static void setProperty(final Object bean, final String property, final Object value) {
		if(Map.class.isAssignableFrom(bean.getClass())){
			((Map)bean).put(property, value);
		}else if(List.class.isAssignableFrom(bean.getClass())){
			((List)bean).set(Integer.parseInt(property), value);
		}else if(bean.getClass().isArray()){
			Array.set(bean, Integer.parseInt(property), value);
		}else{
			setSimpleProperty(bean, property, value);
		}
	}
	
	private static Object getSimpleProperty(Object bean, String property){
		try {
			PropertyDescriptor pd = new PropertyDescriptor(property, bean.getClass());
			Method method = pd.getReadMethod();
			return method.invoke(bean);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	private static void setSimpleProperty(Object bean, String property, Object value){
		try {
			PropertyDescriptor pd = new PropertyDescriptor(property, bean.getClass());
			Method method = pd.getWriteMethod();
			method.invoke(bean, value);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	//遍历
	private static void traverse(Class clazz, Object bean, boolean ignoreReadOnly, Handle handle) throws Exception{
        BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        for(PropertyDescriptor prop:pds){
        	Method writeMethod = prop.getWriteMethod();
        	if(ignoreReadOnly && writeMethod==null){
        		continue;
        	}
        	Method readMethod = prop.getReadMethod();
        	Object pv = null;
			try {
				if(readMethod!=null && bean!=null){
					pv = readMethod.invoke(bean, Empty.ARRAY);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
        	handle.handle(clazz, writeMethod, readMethod, prop.getName(), pv);
        }
	}
	
	private static void traverse(Class clazz, Object[] beanArray, boolean ignoreReadOnly, Handle handle){
		BeanInfo beanInfo;
		try {
			beanInfo = Introspector.getBeanInfo(clazz);
		} catch (IntrospectionException e) {
			throw new RuntimeException(e);
		}
        
        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        for(PropertyDescriptor prop:pds){
        	Method writeMethod = prop.getWriteMethod();
        	if(ignoreReadOnly && writeMethod==null){
        		continue;
        	}
        	Method readMethod = prop.getReadMethod();
        	Object[] pvArray = new Object[beanArray.length];
        	for(int i=0;i<beanArray.length;i++){
        		Object bean = beanArray[i];
            	Object pv = null;
    			try {
    				if(readMethod!=null && bean!=null){
    					pv = readMethod.invoke(bean, Empty.ARRAY);
    				}
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    			pvArray[i] = pv;
        	}
        	handle.handle(clazz, writeMethod, readMethod, prop.getName(), pvArray);
        }
	}
	
	private interface Handle{
		void handle(Class clazz, Method writeMethod, Method readMethod, String property, Object pv);
	}
	
	/**
	 * 把source的属性merge到target
	 * @param source
	 * @param target
	 * @throws Exception
	 */
	public void merge(final Object source, final Object target){
		final Object[] beanArray = new Object[]{source, target};
		Handle handle = new Handle(){
			public void handle(Class clazz, Method writeMethod, Method readMethod,String property, Object pv) {
				Object[] pvArray = (Object[]) pv;
				Object sourcePV = pvArray[0];
				Object targetPV = pvArray[1];
				if(sourcePV!=null){
					if(targetPV==null){
						setProperty(target, property, sourcePV);
					}else{
						if(targetPV instanceof Collection){
							Collection sCol = (Collection)sourcePV;
							Collection tCol = (Collection)targetPV;
							for(Object item:sCol){
								if(!tCol.contains(item)){
									tCol.add(item);
								}
							}
						}else if(targetPV instanceof Map){
							Map sMap = (Map)sourcePV;
							Map tMap = (Map)targetPV;
							Set<Map.Entry> entrySet = sMap.entrySet();
							for (Map.Entry entry : entrySet) {
								if(!tMap.containsKey(entry.getKey())){
									tMap.put(entry.getKey(), entry.getValue());
								}
							}
						}else{
							merge(sourcePV, targetPV);
						}
					}
				}
			}
		};
		traverse(target.getClass(), beanArray, true ,handle);
	}

	//TODO 待验证
	public static Object getNestedProperty(Object bean, String property){
		Object pv = bean;
		String[] pps = property.split("\\.");
		for(int i=0;i<pps.length;i++){
			if(pv==null){
				break;
			}
			String pp = pps[i];
			pv = getProperty(pv, pp);
		}
		return pv;
	}
	
	//TODO 待验证
	public static void setNestedProperty(Object bean, String property, Object value){
		String[] pps = property.split("\\.");
		String pName = pps[pps.length-1];
		Object pv = bean;
		for(int i=0; i<pps.length-1; i++){
			if(pv==null){
				break;
			}
			String pp = pps[i];
			pv = getProperty(pv, pp);
		}
		if(pv!=null){
			setProperty(pv, pName, value);
		}
	}
	
	public static void updateProperty(Object bean, String property, Object value){
		String[] pps = property.split("\\.");
		updateProperty(bean, pps, 0, value, true);
	}
	
	private static void updateProperty(Object bean, String[] pps, int offset, Object value, boolean newProperty){
		int len = pps.length;
		Object pv = bean;
		for(int i=offset; i<len; i++){
			if(pv==null){
				break;
			}
			String pp = pps[i];
			if(!"*".equals(pp)){
				if(i != len-1){
					pv = getProperty(pv, pp);
					if(pv==null && newProperty){
						pv = getDefaultValue(pv, pp);
					}
				}else{
					setProperty(pv, pp, value);
					return;
				}
			}else{
				if(List.class.isAssignableFrom(pv.getClass())){
					for(Object item : (List)pv){
						updateProperty(item, pps, i+1, value, newProperty);
					}
					return;
				}else if(pv.getClass().isArray()){
					for(Object item : (Object[])pv){
						updateProperty(item, pps, i+1, value, newProperty);
					}
					return;
				}else{
					throw new RuntimeException("type error!");
				}
			}
		}
	}

	private static Object getDefaultValue(Object pv, String pp) {
		// TODO Auto-generated method stub
		return null;
	}
	
	public static Map getPropertyMap(List list, String keyProperty, String valProperty){
		Map map = groupByUniqueProperty(list, keyProperty);
		Set<Entry> entrySet = map.entrySet();
		for(Entry entry:entrySet){
			Object value = getProperty(entry.getValue(), valProperty);
			entry.setValue(value);
		}
		return map;
	}
	
}
